package com.wanger.swa.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wanger.swa.dto.templateDynamicField.req.TemplateDynamicFieldAddReq;
import com.wanger.swc.config.myBatisPlus.autoDDL.model.MyColumn;
import com.wanger.swc.config.myBatisPlus.autoDDL.model.MyTable;
import com.wanger.swc.exception.BusEx;
import com.wanger.swc.model.tips.TipInfo;
import com.wanger.swc.util.mybatisplus.MyWrapper;
import com.wanger.swc.util.template.FileData;
import com.wanger.swc.util.template.MyFileUtil;
import com.wanger.swc.util.validation.MyVerifyUtil;
import com.wanger.swa.convert.TemplateConvert;
import com.wanger.swa.domain.myEnum.TemplateEnum;
import com.wanger.swa.domain.po.TemplateDynamicFieldPO;
import com.wanger.swa.dto.template.query.TemplateQuery;
import com.wanger.swa.dto.template.req.TemplateAddReq;
import com.wanger.swa.dto.template.req.TemplateUpdateContentReq;
import com.wanger.swa.dto.template.req.TemplateUpdatePidReq;
import com.wanger.swa.dto.template.req.TemplateUpdateReq;
import com.wanger.swa.dto.template.resp.TemplateResp;
import com.wanger.swa.domain.po.TemplatePO;
import com.wanger.swa.domain.mapper.TemplateMapper;
import com.wanger.swa.dto.template.resp.tips.systemTool.SysToolInfo;
import com.wanger.swa.dto.template.resp.tips.fileInfo.FileInfoResp;
import com.wanger.swa.dto.templateDynamicField.resp.TemplateDynamicFieldResp;
import com.wanger.swa.service.ise.DataSourceService;
import com.wanger.swa.service.ise.TemplateDynamicFieldService;
import com.wanger.swa.service.ise.TemplateService;
import com.wanger.swa.dto.template.resp.TemplateTreeResp;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Author: wanger
 * @Date: 2024/9/15 12:52
 * @Description: 模板
 */
@Service
public class TemplateServiceImpl extends ServiceImpl<TemplateMapper, TemplatePO> implements TemplateService {

    @Resource
    private TemplateConvert templateConvert;
    @Resource
    private TemplateDynamicFieldService templateDynamicFieldService;

    @Override
    @Transactional
    public TemplateResp addTemplate(TemplateAddReq addDTO) {
        TemplatePO template = templateConvert.toSaveEntity(addDTO);
        //同一个pid，名称不能重复
        if (CollUtil.isNotEmpty(this.list(MyWrapper.create(TemplatePO.class)
                .eq(template::getPid)
                .eq(template::getName)
        ))) BusEx.throwEx("当前分组下名称不能重复！");
        //文件不能添加子目录
        if (!TemplatePO.defaultPid.equals(template.getPid())) {
            TemplatePO pt = this.getById(addDTO.getPid());
            if (TemplateEnum.WJ.getValue().equals(pt.getType())) BusEx.throwEx("文件不能添加子目录！");
        }
        this.save(template);
        //保存占位符
        if (CollUtil.isNotEmpty(addDTO.getDynamicFields())) {
            addDTO.getDynamicFields()
                    .forEach(item -> item.setTemplateId(template.getId()));
        }
        templateDynamicFieldService.addTemplateDynamicFieldList(addDTO.getDynamicFields());
        return templateConvert.toDetailsDTO(template);
    }

    @Override
    @Transactional
    public TemplateResp updateTemplate(TemplateUpdateReq updateDTO) {
        TemplatePO template = templateConvert.toUpdateEntity(updateDTO);
        //同一个pid，名称不能重复
        if (CollUtil.isNotEmpty(this.list(MyWrapper.create(TemplatePO.class)
                .eq(template::getPid)
                .eq(template::getName)
                .ne(template::getId)
        ))) BusEx.throwEx("当前分组下名称不能重复！");
        template.setUpdateTime(null);
        this.updateById(template);
        //保存占位符
        if (CollUtil.isNotEmpty(updateDTO.getDynamicFields())) {
            updateDTO.getDynamicFields().forEach(item -> item.setTemplateId(template.getId()));
        }
        templateDynamicFieldService.removeByTemplateId(template.getId());
        templateDynamicFieldService.addTemplateDynamicFieldList(updateDTO.getDynamicFields());
        return templateConvert.toDetailsDTO(template);
    }

    @Override
    public TemplateResp getOneTemplate(String id) {
        TemplatePO template = this.getById(id);
        return templateConvert.toDetailsDTO(template);
    }

    @Override
    public String getTemplateContentById(String id) {
        MyVerifyUtil.nullVerify(id, "模板id不能为空！");
        List<TemplatePO> list = this.list(MyWrapper.create(TemplatePO.class)
                .select(TemplatePO::getContent)
                .eq(TemplatePO::getId, id));
        for (TemplatePO tpo : list) if (tpo != null) return tpo.getContent();
        return null;
    }

    @Override
    public IPage<TemplateResp> pageTemplate(TemplateQuery query) {
        IPage<TemplatePO> page = page(new Page<>(query.getCurrent(), query.getSize()));
        return templateConvert.toPageDTO(page);
    }

    @Override
    public List<TemplateResp> listTemplate(TemplateQuery query) {
        return templateConvert.toListDTO(this.list(MyWrapper.create(TemplatePO.class)
                .eq(query::getPid)
                .orderByAsc(TemplatePO::getName)
        ));
    }

    @Override
    @Transactional
    public void deleteTemplate(String id) {
        MyVerifyUtil.nullVerify(id, "模板id不能为空！");
        //递归查询所有模板递归删除
        this.queryAndDelete(id);
    }

    /**
     * 递归查询与删除所有子模板
     *
     * @param pid 父id
     */
    @Override
    @Transactional
    public void queryAndDelete(String pid) {
        List<TemplatePO> list = this.list(MyWrapper.create(TemplatePO.class).eq(TemplatePO::getPid, pid));
        Set<String> ids = list.stream().map(TemplatePO::getId).collect(Collectors.toSet());
        ids.add(pid);
        for (TemplatePO po : list) {
            queryAndDelete(po.getId());
        }
        templateDynamicFieldService.removeByTemplateIds(ids);
        this.removeByIds(ids);
    }

    @Override
    public List<TemplateTreeResp> getTree(String pid) {
        if (StrUtil.isBlank(pid)) pid = TemplatePO.defaultPid;
        TemplateTreeResp treeResp = BeanUtil.copyProperties(this.getById(pid), TemplateTreeResp.class);
        if (treeResp == null) {
            treeResp = new TemplateTreeResp();
            treeResp.setId(pid);
        }
        return buildTemplateTree(treeResp, true).getChildren();
    }

    /**
     * 构建模板树
     *
     * @param template 模板
     * @param viewTree true：不查询content，同时查询reminder
     * @return 构建后的模板
     */
    public TemplateTreeResp buildTemplateTree(TemplateResp template, boolean viewTree) {
        TemplateTreeResp treeResp = BeanUtil.copyProperties(template, TemplateTreeResp.class);
        MyWrapper<TemplatePO> select = MyWrapper.create(TemplatePO.class);
        if (viewTree) {
            select.select(TemplatePO::getId,
                    TemplatePO::getPid,
                    TemplatePO::getName,
                    //TemplatePO::getContent,//不查询内容以加快页面渲染速度
                    TemplatePO::getType,
                    TemplatePO::getZip,
                    TemplatePO::getSkip,
                    TemplatePO::getUnfoldField,
                    TemplatePO::getUnfoldFieldAlias,
                    TemplatePO::getReminder);
        } else {
            select.select(TemplatePO::getId,
                    TemplatePO::getPid,
                    TemplatePO::getName,
                    TemplatePO::getContent,
                    TemplatePO::getType,
                    TemplatePO::getZip,
                    TemplatePO::getSkip,
                    TemplatePO::getUnfoldField,
                    TemplatePO::getUnfoldFieldAlias,
                    TemplatePO::getReminder);
        }
        Map<String, List<TemplateTreeResp>> tMap = BeanUtil.copyToList(this.list(
                        select.orderByAsc(TemplatePO::getName)), TemplateTreeResp.class)
                .stream().collect(Collectors.groupingBy(TemplateTreeResp::getPid));

        Map<String, List<TemplateDynamicFieldResp>> tdMap = BeanUtil.copyToList(templateDynamicFieldService.list(
                        MyWrapper.create(TemplateDynamicFieldPO.class)
                                .orderByAsc(TemplateDynamicFieldPO::getSortValue)), TemplateDynamicFieldResp.class)
                .stream().collect(Collectors.groupingBy(TemplateDynamicFieldPO::getTemplateId));

        return buildTemplateTreeImpl(treeResp, tMap, tdMap);
    }

    /**
     * 构建模板数
     *
     * @param treeResp 模板
     * @param tMap     原始模板数
     * @param tdMap    动态字段树
     * @return 构建后的模板
     */
    private TemplateTreeResp buildTemplateTreeImpl(TemplateTreeResp treeResp,
                                                   Map<String, List<TemplateTreeResp>> tMap,
                                                   Map<String, List<TemplateDynamicFieldResp>> tdMap) {
        List<TemplateTreeResp> tList = tMap.get(treeResp.getId());
        if (CollUtil.isNotEmpty(tList)) {
            for (TemplateTreeResp resp : tList) {
                buildTemplateTreeImpl(resp, tMap, tdMap);
            }
            treeResp.setChildren(tList);
        }
        if (CollUtil.isEmpty(treeResp.getDynamicFields())) {
            treeResp.setDynamicFields(tdMap.get(treeResp.getId()));
        }
        return treeResp;
    }

    @Override
    public boolean updateTemplateContent(TemplateUpdateContentReq updateDTO) {
        MyVerifyUtil.nullVerify(updateDTO, "请求数据不能为空！");
        MyVerifyUtil.nullVerify(updateDTO::getId);
        updateDTO.setContent(updateDTO.getContent() == null ? "" : updateDTO.getContent());
        TemplatePO po = this.getById(updateDTO.getId());
        BeanUtil.copyProperties(updateDTO, po);
        po.setUpdateTime(null);
        return this.updateById(po);
    }

    @Resource
    private DataSourceService dataSourceService;

    /**
     * 数据源相关提示
     *
     * @param databaseId 数据源id
     * @return 提示内容
     */
    @Override
    public Object tipsDataInfo(String databaseId) {
        if (StrUtil.isBlank(databaseId)) {
            return CollUtil.toList(new MyTable(
                    "数据库原始表名",
                    "数据库表名",
                    "数据库表注释",
                    CollUtil.newHashSet("字段导包1", "字段导包2"),
                    CollUtil.toList(new MyColumn(
                            "数据库字段名",
                            "数据库字段类型",
                            "数据库字段java类型",
                            "数据库字段注释"))));
        } else {
            //查询id对应的数据类型，并查询数据后返回
            return dataSourceService.getDataSourceById(databaseId).getDataObj();
        }
    }

    @Override
    public FileInfoResp tipsFileInfo(String templateId) {
        FileInfoResp resp = new FileInfoResp();
        resp.setTitle(new TipInfo("file", "fileParent=>文件父;file=>当前文件;fileChildren=>文件子"));
        if (StrUtil.isNotBlank(templateId)) {
            resp.getTips().addAll(traceabilityDynamicFieldList(this.getOne(
                    MyWrapper.create(TemplatePO.class)
                            .select(TemplatePO::getId,
                                    TemplatePO::getPid,
                                    TemplatePO::getName,
                                    TemplatePO::getUnfoldField,
                                    TemplatePO::getUnfoldFieldAlias)
                            .eq(TemplatePO::getId, templateId)
            )).stream()
                    .collect(Collectors.toMap(TipInfo::getCode, Function.identity(), (v1, v2) -> v1))
                    .values()
                    .stream()
                    .sorted(Comparator.comparing(TipInfo::getCode))
                    .collect(Collectors.toList()));
        }
        resp.getChildren().add(new FileInfoResp(new TipInfo("file", "当前模板文件"),
                CollUtil.toList(
                        new TipInfo("file.id", "文件id"),
                        new TipInfo("file.pid", "文件pid"),
                        new TipInfo("file.name", "文件name"),
                        new TipInfo("file.content", "文件内容"),
                        new TipInfo("file.type", "文件类型"),
                        new TipInfo("file.zip", "文件是否压缩为zip"),
                        new TipInfo("file.skip", "文件是否跳过"),
                        new TipInfo("file.reminder", "文件提示说明"),
                        new TipInfo("file.unfoldField", "展开字段展开，需要根据该字段生成多文件时使用"),
                        new TipInfo("file.unfoldFieldAlias", "展开后的别名")
                )));
        resp.getChildren().add(new FileInfoResp(new TipInfo("db", "选定的数据源"),
                CollUtil.toList(
                        new TipInfo("db.id", "数据源id"),
                        new TipInfo("db.name", "数据源名称"),
                        new TipInfo("db.url", "数据源url"),
                        new TipInfo("db.username", "数据源用户名"),
                        new TipInfo("db.password", "数据源密码"),
                        //new TipInfo("db.type", "数据源类型【数据结构/数据实例】"),
                        //new TipInfo("db.contentType", "数据源内容类型【数据结构(mysql,postgresql等)/数据实例(yml,json等)】"),
                        new TipInfo("db.contentType", "数据源内容类型(mysql,postgresql等)"),
                        //new TipInfo("db.content", "数据源为数据实例时为具体的yml或者json等内容"),
                        new TipInfo("db.tablePrefix", "数据源要删除的表前缀(多个使用逗号隔开)"),
                        new TipInfo("db.exclusionTable", "数据源要排除的表(多个使用逗号隔开)")
                )));
        return resp;
    }

    /**
     * 递归溯源动态字段
     *
     * @param template 模板
     * @return 动态字段
     */
    private List<TipInfo> traceabilityDynamicFieldList(TemplatePO template) {
        List<TipInfo> list = new ArrayList<>();
        if (template == null || StrUtil.isBlank(template.getId())) return list;
        list.addAll(templateDynamicFieldService.list(MyWrapper.create(TemplateDynamicFieldPO.class)
                        .select(TemplateDynamicFieldPO::getPlaceholder, TemplateDynamicFieldPO::getName)
                        .eq(TemplateDynamicFieldPO::getTemplateId, template.getId())
                        .orderByAsc(TemplateDynamicFieldPO::getSortValue))
                .stream()
                .map(d -> new TipInfo(d.getPlaceholder(), template.getName() + "=>" + d.getName()))
                .collect(Collectors.toList())
        );
        if (StrUtil.isNotBlank(template.getUnfoldField()) && StrUtil.isNotBlank(template.getUnfoldFieldAlias())) {
            list.add(new TipInfo(template.getUnfoldFieldAlias(), template.getName() + "=>展开字段"));
        }
        if (StrUtil.isBlank(template.getPid())) return list;
        list.addAll(traceabilityDynamicFieldList(this.getOne(
                MyWrapper.create(TemplatePO.class)
                        .select(TemplatePO::getId,
                                TemplatePO::getPid,
                                TemplatePO::getName,
                                TemplatePO::getUnfoldField,
                                TemplatePO::getUnfoldFieldAlias)
                        .eq(TemplatePO::getId, template.getPid()))));
        return list;
    }

    @Resource
    private ApplicationContext applicationContext;

    @Override
    public Collection<SysToolInfo> tipsSystemTool() {
        return applicationContext.getBeansOfType(SysToolInfo.class).values();
    }

    @Override
    public void exportTemplate(String id, OutputStream out) {
        MyVerifyUtil.nullVerify(id, "模板id不能为空！");
        TemplateTreeResp treeResp = buildTemplateTree(this.getOneTemplate(id), false);
        MyFileUtil.zipFile(MyFileUtil.builderFile(new FileData("SWTemplate.json")
                .setFileContent(JSON.toJSONString(treeResp))), out);
    }

    @Override
    @Transactional
    public boolean importTemplate(String id, MultipartFile file) {
        MyVerifyUtil.nullVerify(id, "模板id不能为空！");
        MyVerifyUtil.nullVerify(file, "文件对象不能为空！");
        TemplateResp template = this.getOneTemplate(id);
        MyVerifyUtil.nullVerify(template, "选定模板目录不存在！");
        try {
            InputStream zpiIs = file.getInputStream();
            File unZip = unZip(zpiIs);
            String content = getFileContent(unZip);
            TemplateTreeResp tree = JSON.parseObject(content, TemplateTreeResp.class);
            if (tree == null) tree = fileToTemplateTree(unZip);
            if (tree == null) BusEx.throwEx("不导入空文件！");
            saveTree(id, tree);
        } catch (IOException e) {
            BusEx.throwEx("文件导入失败！", e);
        }
        return true;
    }

    /**
     * 解压文件
     *
     * @param zpiIs 压缩包输入流
     * @return 解压后的文件
     */
    private static File unZip(InputStream zpiIs) {
        File unZip = ZipUtil.unzip(zpiIs, new File(MyFileUtil.getTmpFileNamePath(true)), Charset.defaultCharset());
        File[] files = unZip.listFiles();
        if (files != null) {
            for (File file : files) {
                return file;
            }
        }
        throw BusEx.create("解压缩文件失败！");
    }

    /**
     * 把普通文件解析为模板树
     *
     * @param file 普通文件
     * @return 模板树
     */
    private TemplateTreeResp fileToTemplateTree(File file) {
        if (file == null) return null;
        TemplateTreeResp resp = new TemplateTreeResp();
        resp.setName(file.getName());//必填
        if (file.isDirectory()) {
            resp.setType(TemplateEnum.ML.getValue());//必填
            File[] files = file.listFiles();
            if (files != null) {
                resp.setChildren(Arrays.stream(files)
                        .collect(Collectors.toList())
                        .parallelStream()
                        .map(this::fileToTemplateTree)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toList()));
            }
        } else {
            resp.setType(TemplateEnum.WJ.getValue());//必填
            resp.setContent(FileUtil.readUtf8String(file));//type为wj时填
        }
        return resp;
    }

    /**
     * 递归保存树结构，同时使用新的id进行保存
     *
     * @param pid  上级id
     * @param tree 树结构
     */
    @Override
    @Transactional
    public void saveTree(String pid, TemplateTreeResp tree) {
        MyVerifyUtil.nullVerify(pid, "上级id不能为空！");
        MyVerifyUtil.nullVerify(tree, "树结构不能为空！");
        TemplatePO po = BeanUtil.copyProperties(tree, TemplatePO.class);
        po.setId(null);//置空使其生成新的id
        po.setPid(pid);
        po.setCreateTime(null);
        po.setUpdateTime(null);
        if (CollUtil.isNotEmpty(this.list(MyWrapper.create(TemplatePO.class)
                .select(TemplatePO::getId)
                .eq(TemplatePO::getPid, pid)
                .eq(tree::getName)))) BusEx.throwEx("模板导入后与现有的模板名称冲突！");
        this.save(po);//保存，同时生成id/时间等
        List<TemplateDynamicFieldResp> fields = tree.getDynamicFields();
        if (CollUtil.isNotEmpty(fields)) {
            templateDynamicFieldService.addTemplateDynamicFieldList(fields.stream()
                    .map(field -> {
                        TemplateDynamicFieldAddReq fieldAddReq = BeanUtil.copyProperties(field, TemplateDynamicFieldAddReq.class);
                        fieldAddReq.setId(null);
                        fieldAddReq.setTemplateId(po.getId());
                        fieldAddReq.setCreateTime(null);
                        fieldAddReq.setUpdateTime(null);
                        return fieldAddReq;
                    })
                    .collect(Collectors.toList()));
        }
        List<TemplateTreeResp> children = tree.getChildren();
        if (CollUtil.isNotEmpty(children)) {
            for (TemplateTreeResp child : children) {
                saveTree(po.getId(), child);
            }
        }
    }

    /**
     * 递归找到第一个以.json结尾的文件
     *
     * @param file 文件/文件夹
     * @return 文件内容
     */
    private static String getFileContent(File file) {
        if (file != null) {
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files != null) {
                    for (File listFile : files) {
                        return getFileContent(listFile);
                    }
                }
            } else if (file.isFile() && file.getName().endsWith(".json")) {
                return FileUtil.readUtf8String(file);
            }
        }
        return "";
    }

    @Override
    public boolean updatePid(TemplateUpdatePidReq req) {
        MyVerifyUtil.nullVerify(req, "参数不能为空！");
        String id = MyVerifyUtil.nullVerify(req::getId);
        String pid = MyVerifyUtil.nullVerify(req::getPid);
        TemplatePO entity = this.getById(id);
        MyVerifyUtil.nullVerify(entity, "操作模板不存在！");
        entity.setUpdateTime(null);
        entity.setPid(pid);
        if (CollUtil.isNotEmpty(this.list(MyWrapper.create(TemplatePO.class)
                .eq(TemplatePO::getPid, pid)
                .eq(entity::getName)))) BusEx.throwEx("目标分组下有文件与当前文件名称冲突！");
        return this.updateById(entity);
    }
}
